﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Proggy.Data.Entities;
using Proggy.Data.Abstract;
using System.Web.Mvc;
using Newtonsoft.Json;
using Proggy.Web.Cms.Repositories;
using Proggy.Web.Cms.Repositories.ContentModel;
using Proggy.Web.Cms.ContentModel;
using Proggy.Web.Cms.Cache;
using System.Web.Routing;
using Proggy.Web.Cms.AppHelpers;
using System.Web;
using Proggy.Web.Cms.ViewModels;

namespace Proggy.Web.Cms.Service
{
    /// <summary>
    /// Repository for content CRUD
    /// </summary>
    public class ContentCrudService : IContentCrudService
    {
        #region public methods

        /// <summary>
        /// Returns content-index view model
        /// </summary>
        /// <returns></returns>
        public ContentHomeViewModel GetContentHomeViewModel()
        {
            return new ContentHomeViewModel(this._nodeRepo);
        }

        /// <summary>
        /// Returns site map view model
        /// </summary>
        /// <param name="open"></param>
        /// <param name="selected"></param>
        /// <returns></returns>
        public SiteMapViewModel GetSiteMapViewModel(string areaId, string open, string selected, bool includeContentMenu, bool useLiveLinksAndNodes)
        {
            return new SiteMapViewModel(areaId, open, selected, includeContentMenu, useLiveLinksAndNodes, this._nodeRepo, this.ContentModelTypeRepository);
        }

        /// <summary>
        /// Returns root node or null
        /// </summary>
        /// <returns></returns>
        public PublishableNode GetRootNode()
        {
            return this._nodeRepo.GetRootNode();
        }

        /// <summary>
        /// Returns a select list from available child view model types array
        /// </summary>
        /// <param name="availableType"></param>
        /// <returns></returns>
        public IEnumerable<SelectListItem> GetAllowedChildAvailableTypesSelectList(AvailableContentModel availableType)
        {
            // get allowed child types
            var allowedChildTypes = this._contentModelRepo.GetAvailableChildTypesFromContentModel(availableType);

            // 
            return allowedChildTypes.Select(x => new SelectListItem
            {
                Text = x.FriendlyName,
                Value = x.TypeAlias
            });
        }

        /// <summary>
        /// Returns allowed root node types as select list item
        /// </summary>
        /// <param name="availableType"></param>
        /// <returns></returns>
        public IEnumerable<SelectListItem> GetAllowedRootTypesSelectList()
        {
            // get allowed child types
            var allowedRootTypes = this._contentModelRepo.AvailableContentModels.Where(x => x.CanBeUsedForRootNode);

            // 
            return allowedRootTypes.Select(x => new SelectListItem
            {
                Text = x.FriendlyName,
                Value = x.TypeAlias
            });
        }

        /// <summary>
        /// Returns available view model type from node id
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        public AvailableContentModel GetAvailableContentModelTypeFromNodeId(int nodeId)
        {
            // get the node
            var node = this._nodeRepo.GetPublishableNodeFromNodeId(nodeId);

            // get type
            var typeAlias = node.TypeAlias;

            // get available type
            return this._contentModelRepo.AvailableContentModels.Single(x => x.TypeAlias == typeAlias);
        }

        /// <summary>
        /// Returns select list of available templates for type
        /// </summary>
        /// <param name="availableType"></param>
        /// <returns></returns>
        public IEnumerable<SelectListItem> GetAllowedTemplatesSelectList(AvailableContentModel availableType)
        {
            return availableType.AllowedTemplates.Select(t => new SelectListItem
            {
                Text = t,
                Value = t
            });
        }

        /// <summary>
        /// Returns view model type from alias
        /// </summary>
        /// <param name="typeAlias"></param>
        /// <returns></returns>
        public AvailableContentModel GetAvailableTypeFromTypeAlias(string typeAlias)
        {
            return this._contentModelRepo.AvailableContentModels.Single(x => x.TypeAlias == typeAlias);
        }

        /// <summary>
        /// Create new node
        /// </summary>
        /// <param name="model"></param>
        /// <param name="errorMsg"></param>
        /// <param name="newNodeId"></param>
        /// <returns></returns>
        public bool CreateNewNode(PublishableNode model, int? parentNodeId, ref string errorMsg, ref int newNodeId)
        {
            try
            {
                // get namespaces etc
                var type = this.GetAvailableTypeFromTypeAlias(model.TypeAlias);

                //***** check parent allowed type a final time *****//

                // check parent
                if (parentNodeId != null)
                {
                    // get parent node type
                    var parentNodeType = this.GetAvailableContentModelTypeFromNodeId((int)parentNodeId);

                    // check allowed child types
                    if (!parentNodeType.AllowedChildTypeAliases.Contains(model.TypeAlias))
                    {
                        // error
                        errorMsg = "An error has occured. The parent content you have selected does not allow this child content type. Some data may have changed since you began the creation process. Click 'cancel' to return.";
                        return false;
                    }
                }
                else
                {

                    // should be homepage creation, so check homepage doesn't already exist
                    var alreadyExists = this._nodeRepo.GetRootNode() != null;

                    //
                    if (alreadyExists)
                    {
                        // error
                        errorMsg = "An error has occured. No parent content is selected and the root node already exists. Some data may have changed since you began the creation process. You could try again, or click 'cancel' to return.";
                        return false;
                    }

                }

                // ok, so set type values
                model.TypeAssemblyName = type.AssemblyName;
                model.ControllerNameSpace = type.ControllerNamespace;
                model.ContentModelNameSpace = type.Type.Namespace;

                // pass through to node repo
                return this._nodeRepo.CreateNewNode(model, parentNodeId, ref errorMsg, ref newNodeId);
            }
            catch (Exception ex)
            {
                errorMsg = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// Publish draft
        /// </summary>
        /// <param name="model"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool PublishDraftContent(IContentModelBase model, string currentUsername, ref string errorMsg)
        {
            // get json
            var data = JsonConvert.SerializeObject(model);

            // publish
            if (!this._nodeRepo.PublishDraftNode(model.Id, model.Name, model.UrlSlug, model.UniqueAlias, model.Template, data, currentUsername, ref errorMsg))
            {
                return false;
            }

            // register routes
            this.RefreshRoutesAndCache();

            // run function
            model.OnAfterSuccessfulPublish(this);

            // 
            return true;

        }

        /// <summary>
        /// Save draft
        /// </summary>
        /// <param name="model"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool SaveDraftContent(IContentModelBase model, string currentUsername, ref string errorMsg)
        {
            try
            {
                // get data as string
                var data = JsonConvert.SerializeObject(model);

                // run function
                model.OnAfterSuccesfulSave(this);

                // save
                return this._nodeRepo.SaveDraftNode(model.Id, model.Name, model.UrlSlug, model.UniqueAlias, model.Template, data, currentUsername, ref errorMsg);
            }
            catch (Exception ex)
            {
                // 
                errorMsg = "An error has occured: " + ex.Message;
                return false;

            }
        }

        /// <summary>
        /// Revert to published version
        /// </summary>
        /// <param name="nodeId"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool RevertToPublished(int nodeId, ref string errorMsg)
        {
            return this._nodeRepo.RevertToPublished(nodeId, errorMsg);
        }

        /// <summary>
        /// Returns node from id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public PublishableNode GetPublishableNodeFromId(int id)
        {
            return this._nodeRepo.GetPublishableNodeFromNodeId(id);
        }

        /// <summary>
        /// Trash node
        /// </summary>
        /// <param name="nodeId"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool Trash(int nodeId, string currentUsername, ref string errorMsg)
        {
            // get model for "after" purposes
            var model = this.ContentModelDataRepository.GetContentModelFromNodeId(nodeId, false);

            if (this._nodeRepo.Trash(nodeId, currentUsername, ref errorMsg))
            {
                // refresh routes
                this.RefreshRoutesAndCache();

                //
                if (model != null)
                {
                    // after
                    model.OnAfterSuccessfulTrash(this);
                }

                // ok
                return true;
            }

            return false;
        }

        /// <summary>
        /// Return trashed content
        /// </summary>
        /// <returns></returns>
        public IEnumerable<TrashedNode> GetTrashedContent()
        {
            return this._nodeRepo.TrashedNodes.AsEnumerable();
        }

        /// <summary>
        /// Re register routes
        /// </summary>
        public void RefreshRoutesAndCache()
        {
            // refresh data
            ProggyHttpApplication.AppStartupManager.RefreshData();

            // delete routes
            var routes = RouteTable.Routes.ToList();
            routes.ForEach(r => RouteTable.Routes.Remove(r));

            // register areas
            AreaRegistration.RegisterAllAreas();

            // register routes
            ProggyHttpApplication.RegisterRoutes(RouteTable.Routes);

            // set vm cache
            ProggyHttpApplication.AppStartupManager.SetPublishedContentCache();

            // dispose data
            ProggyHttpApplication.AppStartupManager.DisposeDataContext();

        }

        /// <summary>
        /// Delete trashed content
        /// </summary>
        /// <param name="trashedNodeId"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool DeleteTrashedContent(int trashedNodeId, ref string errorMsg)
        {
            return this._nodeRepo.DeleteTrashedContent(trashedNodeId, ref errorMsg);
        }

        /// <summary>
        /// Sort view model
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public SortChildContentViewModel GetSortChildContentViewModel(int id)
        {
            // start output
            var output = new SortChildContentViewModel
            {
                RequestedParentId = id
            };

            // get parent
            var parent = this._nodeRepo.GetPublishableNodeFromNodeId(output.RequestedParentId);

            // check
            if (parent == null)
            {
                output.ParentExists = false;
                return output;
            }

            // set output parent
            output.ParentNode = parent;
            output.ParentExists = true;


            // check children
            if (output.ParentNode.ChildNodes.Count < 2)
            {
                output.HasChildren = false;
                return output;
            }

            // set children
            output.HasChildren = true;
            output.ChildNodesOrdered = parent.ChildNodes.OrderBy(x => x.OrderInParent).ToList();

            // set array
            output.PostSortedChildren = output.ChildNodesOrdered.Select(x => x.PublishableNodeId).ToArray();

            //
            return output;
        }

        /// <summary>
        /// Save new order
        /// </summary>
        /// <param name="newModel"></param>
        /// <param name="error"></param>
        /// <returns></returns>
        public bool SaveNewChildContentModelOrder(SortChildContentViewModel newModel, ref string error)
        {
            // get model for "after" purposes
            var model = this.ContentModelDataRepository.GetContentModelFromNodeId(newModel.ParentNode.PublishableNodeId, false);

            if (this._nodeRepo.ReOrderChildren(newModel.ParentNode.PublishableNodeId, newModel.PostSortedChildren, ref error))
            {
                this.RefreshRoutesAndCache();
                model.OnAfterSuccessfulSort(this);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Gets a new version of the view model, with child nodes sorted
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public SortChildContentViewModel GetSortedSortChildContentViewModel(SortChildContentViewModel model, int id)
        {
            var newModel = this.GetSortChildContentViewModel(id);
            newModel.PostSortedChildren = model.PostSortedChildren;
            newModel.ReOrderChildrenByPostSortedChildren();
            return newModel;
        }

        /// <summary>
        /// Move content
        /// </summary>
        /// <param name="nodeToMoveId"></param>
        /// <param name="movingToNodeId"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool Move(int nodeToMoveId, int movingToNodeId, ref string errorMsg)
        {
            // get model for "after" purposes
            var model = this.ContentModelDataRepository.GetContentModelFromNodeId(nodeToMoveId, false);

            if (this._nodeRepo.Move(nodeToMoveId, movingToNodeId, ref errorMsg))
            {
                this.RefreshRoutesAndCache();
                model.OnAfterSuccessfulMove(this);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Get trashed node by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TrashedNode GetTrashedNodeById(int id)
        {
            return this._nodeRepo.TrashedNodes.SingleOrDefault(x => x.TrashedNodeId == id);
        }

        /// <summary>
        /// Restore
        /// </summary>
        /// <param name="model"></param>
        /// <param name="error"></param>
        /// <returns></returns>
        public bool RestoreTrashedContent(TrashedNode model, string currentUsername, ref string error, ref int newId)
        {
            // create publishable
            var newPublishable = new PublishableNode
            {
                ContentModelNameSpace = model.ContentModelNameSpace,
                SavedContentModelData = model.SavedContentModelData,
                TypeAlias = model.TypeAlias,
                TypeAssemblyName = model.TypeAssemblyName,
                ControllerNameSpace = model.ControllerNameSpace,
                FirstCreated = model.FirstCreated,
                FirstCreatedUsername = model.FirstCreatedUsername,
                LastUpdated = DateTime.Now,
                LastUpdatedUsername = currentUsername,               
                LevelInTree = 0,
                Template = model.Template,
                UniqueAlias = model.UniqueAlias,
                UrlPortion = model.UrlPortion,
                Name = model.Name                
            };

            try
            {
                // get IContentModel as a check
                var icmModel = this.ContentModelDataRepository.ContentModelConverter.ToPublishedAbstractSingle(
                        newPublishable
                    );
            }
            catch (Exception ex)
            {
                error = "Cannot restore this content - " + ex.Message;
                return false;
            }

            //
            return this._nodeRepo.Restore(model, newPublishable, ref error, ref newId);
        }

        #endregion

        #region public properties / constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="nodeRepo"></param>
        public ContentCrudService(INodeRepository nodeRepo, IContentModelTypeRepository contentModelRepo, IContentModelDataRepository contentModelDataRepo)
        {
            this._nodeRepo = nodeRepo;
            this._contentModelRepo = contentModelRepo;
            this._contentModelDataRepo = contentModelDataRepo;
        }

        /// <summary>
        /// Public VM repo
        /// </summary>
        public IContentModelTypeRepository ContentModelTypeRepository { get { return this._contentModelRepo; } }

        /// <summary>
        /// Content model data repository
        /// </summary>
        public IContentModelDataRepository ContentModelDataRepository { get { return this._contentModelDataRepo; } }

        #endregion

        #region private fields

        /// <summary>
        /// The node repository
        /// </summary>
        private INodeRepository _nodeRepo;

        /// <summary>
        /// View model repo
        /// </summary>
        private IContentModelTypeRepository _contentModelRepo;

        /// <summary>
        /// Content model data repo
        /// </summary>
        private IContentModelDataRepository _contentModelDataRepo;

        #endregion
    }
}
